//通过匿名函数隐藏内部变量
(function() {
	//保存全局对象
	var root = this;

	//如果之前有_这个变量，把它保存起来
	var previousUnderscore = root._;

	//保存对象的原型，也方便压缩处理
	var ArrayProto = Array.prototype,ObjProto = Object.prototype,FuncProto = Function.prototype;

	//保存一些原型的方法，快速使用
	var
		puch           = ArrayProto.push,
		slice          = ArrayProto.slice,
		toString       = ObjProto.toString,
		hasOwnProperty = ObjProto.hasOwnProperty;

	//要用到的一些原生方法
	var
		nativeIsArray = Array.isArray,
		nativeKeys    = Object.keys,
		nativeBind    = FuncProto.bind,
		nativeCreate  = Object.create;

	//原型没有变过的对象，用来做代理原型交换
	var Ctor = function(){};

	//作用域安全的构造函数，为了防止new 和没有new的带来this指针的区别
	//new 一个对象就可以直接用里面的方法，不用再传obj进去了
	var _ = function(obj){
		if(obj instanceof _) return obj;	//防止重复构建
		if (!(this instanceof _)) return new _(obj);	//防止没有new时，然后重新用new构造
		this._wrapped = obj;	//将传来的obj，存在属性_wrapped里面
	};


	//这里是配置node.js和一些模块化的环境，但是要注意一下，typeof一个未定义的变量不会报错，只会变成undefined
	if(typeof exports !== 'undefined'){
		if (typeof module !== 'undefined' && module.exports) {
			exports = module.exports = _;
		}
		exports._ = _;
	}else{
		root._=_;
	}

	//当前版本
	_.VERSION = '1.8.2';

	//优化cb
	//因为undefined不是保留字，在个别浏览器会被修改，但是用void 0，可以得到一个纯正的undefined值
	var optimizeCb = function(func,context,argCount){
		if (context === void 0)	return func;	//匹配context是undefined
		switch (argCount == null ? 3 : argCount){
			case 1 : return function(value){
				return func.call(context,value);
			};
			case 2 : return function(value,other){
				return func.call(context,value,other);
			};
			case 3 : return function(value,index,collection){
				return func.call(context,value,index,collection);
			};
			case 4 : return function(accumulator,value,index,collection){
				return func.call(context,accumulator,value,index,collection);
			}
		}
		//argCount除了null，1，2，3，4，undefined，都会进来
		return function(){
			return func.apply(context,arguments);
		}
	};

	//常用的内部cb方法，通过传入的value做判断
	//是空值直接放回空值
	//是函数通过optimizeCb进行处理返回
	//是对象，返回一个判断函数，判断另外一个对象里是否有这些属性
	//是字符串，返回的函数传入对象，吐出该对象的属性值
	var cb = function(value,context,argCount){
		if (value == null) return _.identity;
		if(_.isFunction(value)) return optimizeCb(value,context,argCount);
		if(_.isObject(value)) return _.matcher(value);	//
		return _.property(value);
	};
	_.iteratee = function(value, context) {
		return cb(value, context, Infinity);
	};

	// 放回函数作用合并多个对象，同个属性，最后放回一个对象---98
	var createAssigner = function(keysFunc, undefinedOnly) {
		return function(obj) {
			var length = arguments.length;
			if (length < 2 || obj == null) return obj;
			for (var index = 1; index < length; index++) {
				var source = arguments[index],
				keys = keysFunc(source),
				l = keys.length;
				for (var i = 0; i < l; i++) {
					var key = keys[i];
					if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
				}
			}
			return obj;
		};
	};

	//判断是否是一个类数组---128
	var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
	var isArrayLike = function(collection){
		var length = collection !=null && collection.length;
		return typeof length == 'number' && length >=0 && length <= MAX_ARRAY_INDEX;
	};

	//集合函数
	//--------------------

	//each,遍历数组,返回传入的obj---140
	_.each = _.forEach = function(obj,iteratee,context){
		iteratee = optimizeCb(iteratee,context);
		var i , length;
		if (isArrayLike(obj)) {
			for (i = 0,length = obj.length; i < length; i++) {
				iteratee(obj[i],i,obj);
			}
		}else{
			var keys = _.keys(obj);
			for (i = 0,length = keys.length; i < length; i++) {
				iteratee(obj[keys[i]],keys[i],obj);
			}
		}
		return obj;
	};

	//返回一个处理后的数组---157
	_.map = _.collect = function(obj,iteratee,context){
		iteratee = cb(iteratee,context);
		var keys = !isArrayLike(obj) && _.keys(obj),
	        length = (keys || obj).length,
	        results = Array(length);
		for (var index = 0; index < length; index++) {
	      var currentKey = keys ? keys[index] : index;
	      results[index] = iteratee(obj[currentKey], currentKey, obj);
	    }
	    return results;
	};


	//创造一个到底是向左，还是向右的函数---170
	//-1，从左到右
	//1，从右到左
	function createReduce(dir){
		//进行处理，for循环的函数
		function iterator(obj,iteratee,memo,keys,index,length){
			for(;index >= 0 && index < length;index +=dir){
				var currentKey = keys ? keys[index] :index;
				memo = iteratee(memo,obj[currentKey],currentKey,obj);
			}
			return memo;
		}

		return function(obj,iteratee,memo,context){
			iteratee = optimizeCb(iteratee,context,4);
			var keys = !isArrayLike(obj) && _.keys(obj), //数组，返回false，后面会进行处理			对象，返回一个属性的数组
				length = (keys || obj).length,
				index = dir > 0 ? 0 :length-1;

			//当没有传memo这个参数时，会自动默认给他弄成数组开始的那项
			if(arguments.length < 3){
				memo = obj[keys ? keys[index] : index];
				index +=dir;
			}

			return iterator(obj,iteratee,memo,keys,index,length);

		}
	}

	//从左到右---197
	_.reduce = _.foldl = _.inject = createReduce(1);

	//从右到左---200
	 _.reduceRight = _.foldr = createReduce(-1);

	 //匹配到正确的东西后，返回该key的值---203
	 _.find = _.detect = function(obj,predicate,context){
	 	var key;
	 	if(isArrayLike(ojb)){
	 		key = _.findIndex(obj,predicate,context);
	 	}else{
	 		key = _.findKey(obj,predicate,context);
	 	}
	 	if (key !== void 0 && key !== -1)  return obj[key];
	 };

	//匹配到正确的值后，返回一个数组，包含全部匹配的值---215
	_.filter = _.select = function(obj,predicate,context){
		var results = [];
		predicate = cb(predicate,context);
		_.each(obj,function(value,index,list){
			if(predicate(value,index,list)) results.push(value);
		});
		return results;
	};

	//返回一个数组，匹配不成功的，跟filter相反---225
	_.reject = function(obj,predicate,context){
		return _.filter(obj, _.negate(cb(predicate)),context);
	};

	//如果obj中的所有元素都通过predicate的真值检测就返回true。--244
	_.every = function (obj, predicate, context) {
		predicate = cb(predicate, context);
		var keys = !isArrayLike(obj) && _.keys(obj), //检测时否数组，不是数组就用_.kesy取length值
			length = (keys || obj).length;
		for (var index = 0; index < length; index++) {
			var currentKey = keys ? keys[index] : index;
			if (!predicate(obj[currentKey], currentKey, obj)) return false;
		}
		return true;
	};

	//如果list中有任何一个元素通过 predicate 的真值检测就返回true。一旦找到了符合条件的元素, 就直接中断对list的遍历
	_.some = _.any = function(obj,predicate,context){
		predicate = cb(predicate, context);
		var keys  = !isArrayLike(obj) && _.keys(obj),
			length = (keys || obj).length;
		for(var index = 0;index<length;index++){
			var currentKey = keys ? keys[index] : index;
			if(predicate(obj[currentKey],currentKey,obj)) return true;
		}
		return false;
	};

	//如果obj包含指定的target则返回true---257
	_.contains = _.includes = _.include = function(obj,target,fromIndex){
		if(!isArrayLike(obj)) obj = _.values(obj);
		return _.indexOf(obj,target,typeof fromIndex === 'number' && fromIndex) >=0 ;
	};

	//在list的每个元素上执行methodName方法。
	// 任何传递给invoke的额外参数，invoke都会在调用methodName方法的时候传递给它---263
	_.invoke = function (obj, method) {
		var args = slice.call(arguments, 2);
		var isFunc = _.isFunction(method);
		return _.map(obj, function (value) {
			var func = isFunc ? method : value[method];
			return func == null ? func : func.apply(value, args);
		});
	};

	//pluck也许是map最常使用的用例模型的简化版本，即萃取数组对象中某属性值，返回一个数组---273
	_.pluck = function (obj, key) {
		return _.map(obj, _.property(key));
	};

	//遍历obj中的每一个值，返回一个数组，这个数组包含properties所列出的属性的所有的 键 - 值对---279
	_.where = function(obj, attrs) {
		return _.filter(obj, _.matcher(attrs));
	};

	//遍历整个obj，返回匹配 properties参数所列出的所有 键 - 值 对的第一个值。---285
	_.findWhere = function(obj, attrs) {
		return _.find(obj, _.matcher(attrs));
	};

	//返回list中的最大值。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。
	// 如果list为空，将返回-Infinity --- 290
	_.max = function (obj, iteratee, context) {
		var result = -Infinity, lastComputed = -Infinity,
			value, computed;
		if (iteratee == null && obj != null) {	//只有obj这个参数的时候
			obj = isArrayLike(obj) ? obj : _.values(obj);
			for (var i = 0, length = obj.length; i < length; i++) {
				value = obj[i];
				if (value > result) {
					result = value;
				}
			}
		} else {
			iteratee = cb(iteratee, context);
			_.each(obj, function (value, index, list) {
				computed = iteratee(value, index, list);
				if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
					result = value;
					lastComputed = computed;
				}
			});
		}
		return result;
	};

	//返回list中的最小值。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。
	// 如果list为空，将返回-Infinity --- 315
	_.min = function(obj, iteratee, context) {
		var result = Infinity, lastComputed = Infinity,
			value, computed;
		if (iteratee == null && obj != null) {
			obj = isArrayLike(obj) ? obj : _.values(obj);
			for (var i = 0, length = obj.length; i < length; i++) {
				value = obj[i];
				if (value < result) {
					result = value;
				}
			}
		} else {
			iteratee = cb(iteratee, context);
			_.each(obj, function(value, index, list) {
				computed = iteratee(value, index, list);
				if (computed < lastComputed || computed === Infinity && result === Infinity) {
					result = value;
					lastComputed = computed;
				}
			});
		}
		return result;
	};

	//使用了洗牌算法，随机的是那个shuffled数组，如果当前的---341
	_.shuffle = function(obj) {
		var set = isArrayLike(obj) ? obj : _.values(obj);
		var length = set.length;
		var shuffled = Array(length);
		for (var index = 0, rand; index < length; index++) {
			rand = _.random(0, index);
			if (rand !== index) shuffled[index] = shuffled[rand];
			shuffled[rand] = set[index];
		}
		return shuffled;
	};

	//从 list中产生一个随机样本。传递一个数字表示从list中返回n个随机元素。否则将返回一个单一的随机项。---356
	_.sample = function (obj, n, guard) {
		if (n == null || guard) {
			if (!isArrayLike(obj)) obj = _.values(obj);
			return obj[_.random(obj.length - 1)];
		}
		return _.shuffle(obj).slice(0, Math.max(0, n));
	};

	//返回一个排序后的list拷贝副本。如果传递iteratee参数，iteratee将作为list中每个值的排序依据。
	// 迭代器也可以是字符串的属性的名称进行排序的(比如 length)。---365
	_.sortBy = function (obj, iteratee, context) {
		iteratee = cb(iteratee, context);
		var sortArray = _.map(obj, function (value, index, list) {
			return {
				value: value,
				index: index,
				criteria: iteratee(value, index, list)
			}
		}).sort(function (left, right) {
			var a = left.criteria;
			var b = right.criteria;
			if (a !== b) {
				if (a > b || a === void 0) return 1;
				if (a < b || b === void 0) return -1;
			}
			return left.index - right.index;
		});
		return _.pluck(sortArray, 'value');
	};

	//内部函数，用于集合‘group by’参数---385
	var group = function (behavior) {
		return function (obj, iteratee, context) {
			var result = {};
			iteratee = cb(iteratee, context);
			_.each(obj, function (value, index) {
				var key = iteratee(value, index, obj);
				behavior(result, value, key)
			});
			return result;
		}
	};

	//把一个集合分组为多个集合
	// 通过 iterator 返回的结果进行分组
	// 如果 iterator 是一个字符串而不是函数, 那么将使用 iterator 作为各元素的属性名来对比进行分组.---399
	_.groupBy = group(function (result,value,key) {
		if(_.has(result,key)) result[key].push(value);else result[key] = [value];
	});

	//给定一个list，和 一个用来返回一个在列表中的每个元素键 的iterator 函数（或属性名）， 返回一个每一项索引的对象。
	// 和groupBy非常像，但是当你知道你的键是唯一的时候可以使用indexBy 。--405
	_.indexBy = group(function (result, value, key) {
		result[key] = value;
	});

	//排序一个列表组成一个组
	// 并且返回各组中的对象的数量的计数。类似groupBy
	// 但是不是返回列表的值，而是返回在该组中值的数目。---412
	_.countBy = group(function(result, value, key) {
		if (_.has(result, key)) result[key]++; else result[key] = 1;
	});

	//把list(任何可以迭代的对象)转换成一个数组，在转换 arguments 对象时非常有用。---417
	_.toArray = function (obj) {
		if (!obj) return [];
		if (_.isArray(obj)) return slice.call(obj);
		if (isArrayLike(obj)) return _.map(obj, _.identity);
	};

	//返回list的长度。---425
	_.size = function(obj){
		if (obj == null) return 0;
		return isArrayLike(obj) ? obj.length : _.keys(obj).length;
	}

	//拆分一个数组（array）为两个数组：  第
	//一个数组其元素都满足predicate迭代函数
	//而第二个的所有元素均不能满足predicate迭代函数。---432
	_.partition = function(obj,predicate,context){
		predicate = cb(predicate);
		var pass = [],fail = [];
		_.each(obj,function(value,key,obj){
			(predicate(value,key,obj) ? pass : fail).push(value);
		});
		return [pass,fail];
	}

	// Array Functions
	// ---------------


	//返回array（数组）的第一个元素。
	//传递 n参数将返回数组中从第一个元素开始的n个元素。
	//guard 允许在map运行，因为map的参数有(val, key, obj),n参数到第二个，就不是null了---447
	_.first = _.head = _.take = function(array,n,guard){
		if (array == null) return void 0;
		if (n == null || guard) return array[0];
		return _.initial(array,array.length-n);
	}

	// 返回数组中除了最后一个元素外的其他全部元素。
	// 在arguments对象上特别有用。传递 n参数将从结果中排除从最后一个开始的n个元素。---456
	_.initial = function(array, n, guard) {
		return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
	};

	//返回array（数组）的最后一个元素。
	//传递 n参数将返回数组中从最后一个元素开始的n个元素。---462
	_.last = function(array,n,guard){
		if (array == null) return void 0;
		if (n == null || guard) return array[array.length-1];
		return _.reset(array,Math.max(0,array.length-n));
	}

	//返回array（数组）的最后一个元素。传递 n参数将返回数组中从最后一个元素开始的n个元素---471
	_.reset = function(array,n,guard){
		return slice.call(array,n == null || guard ? 1 : n);
	}

	//返回一个除去所有false值的 array副本。 在javascript中, false, null, 0, "", undefined 和 NaN 都是false值---476
	_.compact = function(array) {
		return _.filter(array, _.identity);
	};

	//内部实现，flatten的方法---481
	var flatten = function(input,shallow,strict,startIndex){
		var output = [],idx = 0;
		for(var i = startIndex || 0,length = input && input.length;i < length;i++){
			var value = input[i];
			if (isArrayLike(value) && ( _.isArray(value) || _.isArguments(value))) {
				if (!shallow) value = flatten(value,shallow,strict);
				var j = 0,len = value.length;
				output.length += len;
				while(j < len){
					output[idx++] = value[j++];
				}
			}else if (!strict) {
				output[idx++] = value;
			}
		}
		return output;
	}

	//将一个嵌套多层的数组 array（数组） (嵌套可以是任何层数)转换为只有一层的数组。
	//如果你传递 shallow参数，数组将只减少一维的嵌套。---501
	_.flatten = function(array,shallow){
		return flatten(array,shallow,false)
	}

	//返回一个删除所有values值后的 array副本。---506
	_.without = function(array) {
		return _.difference(array, slice.call(arguments, 1));
	};

	//返回 array去重后的副本
	//使用 === 做相等测试.
	//如果您确定 array 已经排序, 那么给 isSorted 参数传递 true值, 此函数将运行的更快的算法.
	//如果要处理对象元素, 传递 iteratee函数来获取要对比的属性.---513
	_.uniq = _.unique = function(array, isSorted, iteratee, context) {
		if (array == null) return [];
		if (!_.isBoolean(isSorted)) {
			context = iteratee;
			iteratee = isSorted;
			isSorted = false;
		}
		if (iteratee != null) iteratee = cb(iteratee, context);
		var result = [];
		var seen = [];
		for (var i = 0, length = array.length; i < length; i++) {
			var value = array[i],
			  computed = iteratee ? iteratee(value, i, array) : value;
			if (isSorted) {
			if (!i || seen !== computed) result.push(value);
			seen = computed;
			} else if (iteratee) {
			if (!_.contains(seen, computed)) {
			  seen.push(computed);
			  result.push(value);
			}
			} else if (!_.contains(result, value)) {
			result.push(value);
			}
		}
		return result;
	};

	//返回传入的 arrays（数组）并集：
	//按顺序返回，返回数组的元素是唯一的，可以传入一个或多个 arrays（数组）。
	_.union = function() {
		return _.uniq(flatten(arguments, true, true));
	};


	//返回传入 arrays（数组）交集。结果中的每个值是存在于传入的每个arrays（数组）里。
	_.intersection = function(array) {
		if (array == null) return [];
		var result = [];
		var argsLength = arguments.length;
		for (var i = 0, length = array.length; i < length; i++) {
			var item = array[i];
			if (_.contains(result, item)) continue;
			for (var j = 1; j < argsLength; j++) {
				if (!_.contains(arguments[j], item)) break;
			}
			if (j === argsLength) result.push(item);
		}
		return result;
	};

	//类似于without，但返回的值来自array参数数组，并且不存在于other 数组.--566
	_.difference = function(array) {
		var rest = flatten(arguments, true, true, 1);
		return _.filter(array, function(value){
		  return !_.contains(rest, value);
		});
	};


	// Z将 每个arrays中相应位置的值合并在一起。在合并分开保存的数据时很有用.
	// 如果你用来处理矩阵嵌套数组时, _.zip.apply 可以做类似的效果。---575
	_.zip = function() {
		return _.unzip(arguments);
	};

	// 与zip功能相反的函数，给定若干arrays，返回一串联的新数组，其第一元素个包含所有的输入数组的第一元素，其第二包含了所有的第二元素，依此类推。
	// 通过apply用于传递数组的数组。---581
	_.unzip = function(array) {
		var length = array && _.max(array, 'length').length || 0;
		var result = Array(length);

		for (var index = 0; index < length; index++) {
			result[index] = _.pluck(array, index);
		}
		return result;
	};

	// 将数组转换为对象。传递任何一个单独[key, value]对的列表，或者一个键的列表和一个值得列表。
	// 如果存在重复键，最后一个值将被返回。---594
	_.object = function(list, values) {
		var result = {};
		for (var i = 0, length = list && list.length; i < length; i++) {
			if (values) {
				result[list[i]] = values[i];
			} else {
				result[list[i][0]] = list[i][1];
			}
		}
		return result;
	};

	//返回item在该 array 中的索引值，
	// 如果item不存在 array中就返回-1。使用原生的indexOf 函数，除非它失效。
	// 如果您正在使用一个大数组，你知道数组已经排序，
	// 传递true给isSorted将更快的用二进制搜索..,或者，传递一个数字作为第三个参数，
	// 为了在给定的索引的数组中寻找第一个匹配值。---610
	_.indexOf = function(array,item,isSorted){
		var i = 0,length = array && array.length;
		if(typeof isSorted == 'number'){	//数字就获取该索引值，可以为负数
			i = isSorted < 0 ? Math.max(0,length+isSorted) : isSorted;
		}else if(isSorted && length){	//如果是个已经排序的数组，可以设置isSorted为bol值，用sortedIndex，来处理
			i = _.sortedIndex(array, item);
			return array[i] === item ? i : -1;
		}

		if(item !== item){ //如果是item是NaN
			return _.findIndex(slice.call(array, i), _.isNaN);
		}
		for(;i<length;i++) if(array[i] === item) return i;
		return -1;
	};


	_.lastIndexOf = function(array, item, from) { //625
		var idx = array ? array.length : 0;
		if (typeof from == 'number') {
			idx = from < 0 ? idx + from + 1 : Math.min(idx, from + 1);
		}
		if (item !== item) {
			return _.findLastIndex(slice.call(array, 0, idx), _.isNaN);
		}
		while (--idx >= 0) if (array[idx] === item) return idx;
		return -1;
	};



	//创造寻找数组下标的方法---638
	function createIndexFinder(dir){
		return function(array,predicate,context){
			predicate = cb(predicate,context);
			var length = array != null && array.length;
			var index = dir > 0 ? 0 : length - 1;
			for(;index >= 0 && index < length ;index += dir){
				if(predicate(array[index],index,array)) return index;
			}
			return -1;
		}
	}

	 //左到右搜索，如果有，返回该索引值---651
	 _.findIndex = createIndexFinder(1);

	//右到左搜索，如果有，返回该索引值---653
	 _.findLastIndex = createIndexFinder(-1);


	//使用二分查找确定value在list中的位置序号，value按此序号插入能保持list原有的排序。
	// 如果提供iterator函数，iterator将作为list排序的依据，包括你传递的value 。
	// iterator也可以是字符串的属性名用来排序(比如length)---657
	_.sortedIndex = function (array, obj, iteratee, context) {
		iteratee = cb(iteratee, context, 1);
		var value = iteratee(obj);
		var low = 0, high = array.length;
		while(low < high){
			var mid = Math.floor((low + high) / 2);
			if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
		}
		return low;
	};

	_.range = function(start,stop,step){
		if(arguments.length <= 1){
			stop = start || 0;
			start = 0;
		}
		step = step || 1;

		var length = Math.max(Math.ceil((stop - start) / step), 0);
		var range = Array(length);
		for(var idx = 0;idx < length;idx++,start += step){
			range[idx] = start;
		}
		return range;
	};



	// 返回一个新的predicate函数的否定版本---850
	_.negate = function(predicate) {
		return function() {
			return !predicate.apply(this, arguments);
		};
	};

	// Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. ---898
	var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
	var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
	              'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

	function collectNonEnumProps(obj, keys) {
		var nonEnumIdx = nonEnumerableProps.length;
		var constructor = obj.constructor;
		var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

		// Constructor is a special case.
		var prop = 'constructor';
		if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

		while (nonEnumIdx--) {
			prop = nonEnumerableProps[nonEnumIdx];
			if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
				keys.push(prop);
			}
		}
	}


	//把对象的属性，用一个数组存储起来---921
	_.keys = function(obj) {
		if (!_.isObject(obj)) return [];	//如果非对象，返回一个空数组
		if (nativeKeys) return nativeKeys(obj);	//检查是否有原生的keys方法

		//没有原生keys方法做的处理
		var keys = [];
		for (var key in obj) if (_.has(obj, key)) keys.push(key);	//只有在构造函数里面的才行
		// Ahem, IE < 9.
		if (hasEnumBug) collectNonEnumProps(obj, keys);
		return keys;
	};


	// 把对象的属性，用一个数组存储起来---932
	_.allKeys = function(obj) {
		if (!_.isObject(obj)) return [];	//如果非对象，返回一个空数组

		//没有原生keys方法做的处理
		var keys = [];
		for (var key in obj) keys.push(key);	//原型里面的也会出来
		// Ahem, IE < 9.
		if (hasEnumBug) collectNonEnumProps(obj, keys);
		return keys;
	};

	//返回object对象所有的属性值。
	_.values = function(obj){
		var keys = _.keys(obj);
		var length = keys.length;
		var values = Array(length);
		for(var i = 0;i<length;i++){
			values[i] = obj[keys[i]];
		}
		return values;
	};


	// 合并多个对象（包括原型）---999
	_.extend = createAssigner(_.allKeys);

	// 合并多个对象（不包括原型）---1003
	_.extendOwn = _.assign = createAssigner(_.keys);

	//返回匹配成功的obj对应的key值---1006
	_.findKey = function(obj,predicate,context){
		predicate = cb(predicate,context);
		var keys = _.keys(obj),key;
		for (var i = 0,length = keys.length; i < length; i++) {
			key = keys[i];
			if (predicate( obj[key],kye,obj )) return key;
		}
	}

	// obj有的就不合并，没有就添加上去
  	_.defaults = createAssigner(_.allKeys, true);

	// Returns whether an object has a given set of `key:value` pairs.
	// 当传入的对象和属性值相等，返回bol值---1075
	_.isMatch = function(object, attrs) {
		var keys = _.keys(attrs), length = keys.length;
		if (object == null) return !length;
		var obj = Object(object);
		for (var i = 0; i < length; i++) {
			var key = keys[i];
			if (attrs[key] !== obj[key] || !(key in obj)) return false;
		}
		return true;
	};


	//检查是否是数组---1200
	_.isArray = nativeIsArray || function(obj) {
		return toString.call(obj) === '[object Array]';
	};

	// 是否是对象---1205
	_.isObject = function(obj) {
		var type = typeof obj;
		return type === 'function' || type === 'object' && !!obj;
	};

	//使用each，一次生成那些检测原生的函数---1210
	_.each(['Arguments','Function','String','Number','Date','RegExp','Error'],function(name){
		_['is' + name] = function(obj){
			return toString.call(obj) === '[object' + name +']';
		}
	});

	//修复isArguments小于ie9的bug---1219
	if(!_.isArguments(arguments)){
		_.isArguments = function(obj){
			return _.has(obj,'callee');
		}
	}

	// 修复bug的---1227
	if (typeof /./ != 'function' && typeof Int8Array != 'object') {
		_.isFunction = function(obj) {
			return typeof obj == 'function' || false;
		};
	}

	//检查其参数是否是无穷大---1234
  	_.isFinite = function(obj) {
    	return isFinite(obj) && !isNaN(parseFloat(obj));
  	};

  	//判断是否是NaN,NaN是一个数字，而且不等于自己的数---1239
  	 _.isNaN = function(obj) {
    	return _.isNumber(obj) && obj !== +obj; //+号转化为数字
  	};

  	//检查其参数是否是bol值---1244
  	_.isBoolean = function(obj){
  		return obj === true || obj === false || toString.call(obj) === []
  	};

	// 检查其参数是否是Null---1249
	_.isNull = function(obj) {
		return obj === null;
	};

	// 检查其参数是否是undefined---1254
	_.isUndefined = function(obj) {
		return obj === void 0;
	};

	//检查属性是否是该对象的---1260
	_.has = function(obj, key) {
		return obj != null && hasOwnProperty.call(obj, key);
	};

	//防止冲突
	_.noConflict = function() {
		root._ = previousUnderscore;
		return this;
	};

	//保持迭代器的值
	_.identity = function(value) {
		return value;
	};

  	_.noop = function(){};

	//返回一个函数，调用后是一个恒定值
	_.constant = function(value) {
		return function() {
	  		return value;
		};
	};


	//属性，对象，返回对应的属性值---1288
	_.property = function(key) {
		return function(obj) {
			return obj == null ? void 0 : obj[key];
		};
	};

	//对象，属性，返回对应的属性值---1295
	_.propertyOf = function(obj){
		return obj == null ? function(){} : function(key){
			return  obj[key]
		};
	};

	// Returns a predicate for checking whether an object has a given set of
	// `key:value` pairs.	---1303
	_.matcher = _.matches = function(attrs) {
		attrs = _.extendOwn({}, attrs);
		return function(obj) {
			return _.isMatch(obj, attrs);
		};
	};

	//要执行多少次函数---1311
	_.times = function(n,iteratee,context){
		var accum = Array(Math.max(0,n));
		iteratee = optimizeCb(iteratee,context,1);
		for(vari = 0;i<n;i++) accum[i] = iteratee(i)
		return accum;
	};

	// Return a random integer between min and max (inclusive).
	// 放回一个范围的随机值---1319
	_.random = function(min, max) {
		if (max == null) {
			max = min;
			min = 0;
		}
		return min + Math.floor(Math.random() * (max - min + 1));
	};

	//快速返回当前的时间---1328
	_.now = Date.now || function(){
		return new Date().getTime();
	}



}.call(this));